#include "stdafx.h"
#include "Render.h"

#include "Direct3D9.h"
#include "Direct3D10.h"
#include "..\..\Global\globaldef.h"

#ifdef DEFAULT_D3D10
Render::DeviceVersion Render::g_lastVersion = DeviceVersion::D3D_10;
#else
Render::DeviceVersion Render::g_lastVersion = DeviceVersion::D3D_9;
#endif

Render::tCamera					Render::g_Camera	= Render::tCamera();
std::vector<Render::tResource*>	Render::g_Resources	= std::vector<Render::tResource*>();
std::vector<Render::tShader*>	Render::g_Shaders	= std::vector<Render::tShader*>();
Render::tShader*				Render::g_pDefaultShader		= NULL;

Render::tResource*				Render::g_pProceduralBaseData	= NULL;
Render::tResource*				Render::g_pVertexBuffer_Grid	= NULL;
Render::tShader*				Render::g_pGridShader			= NULL;

#pragma message("use preprocessor stringerizers to achieve these??")
const char* Render::samplers[SAMPLE_COUNT] = 
{
	"SAMPLE_TEXTURE",
	"SAMPLE_WOOD",
	"SAMPLE_NOISE",
	"SAMPLE_GRID",
	"SAMPLE_RAMP",
	"SAMPLE_CHECKER",
	"SAMPLE_RESERVED1",
};

const char* Render::vertex_elements[VERTEX_ELEMENT_COUNT] =
{
	"VERTEX_POSITION",
	"VERTEX_NORMAL",
	"VERTEX_TANGENT",
	"VERTEX_UV1",
	"VERTEX_UV2",
	"VERTEX_UV3",
	//"VERTEX_UV4",
	"VERTEX_COLOR",
	"VERTEX_DEFORMATION",

	"VERTEX_INSTANCE_SCALE_ROTATE",
	"VERTEX_INSTANCE_POSITION",
};

const char* Render::channels[CHANNEL_COUNT] =
{
	"CHANNEL_DECAL",
	"CHANNEL_NORMAL",
	"CHANNEL_SPECULAR",
}; 

Render::tShader::tShader() 
{ 
	referenceCount	= 0;
	pEffect			= NULL;
	VertexFormat	= 0;
	Attributes		= 0;
	pInputDeclaration = NULL;
}

void Render::tShader::Print() {

	printf("------------------\nName:\t%s\nEffect Addr:\t%p\nRef count:%i\nInput Fmt:\t%i\nFeatures:\t%i\n", filename.c_str(), pEffect,referenceCount, VertexFormat, Attributes);
}

const std::string Render::SHADER_DIRECTORY = std::string("Shaders\\");
u_int Render::UpdateShaderRefCount(Render::tShader* pShader, int delta) {

	if(pShader) {

		if(delta == 0)
			return pShader->referenceCount;
		else if(delta < 0) {
			if(pShader->referenceCount == 0) 
				return 0;
			else {
				pShader->referenceCount--;
				return pShader->referenceCount;
			}
		}
		else {
			pShader->referenceCount++;
			return pShader->referenceCount;
		}
	}

	return 0;
}

void Render::DrawGrid() {
	
	UINT passes = 0;
	if(BeginShader(Render::g_pGridShader, false, &passes)) {
			
		D3DXMATRIX ident;
		D3DXMatrixIdentity(&ident);
		SetShaderMatrix("World", &ident);
		uint elementSize = GetVertexFormatSize(Render::g_pGridShader->VertexFormat);
		if(BindVertexBuffer(&Render::g_pVertexBuffer_Grid, &elementSize , 1)) {
		//CD3D9::g_pd3dDevice->SetStreamSource(0, CD3D9::g_pGrid, 0, GRIDVERTEX_SIZE);
			
			for(UINT i = 0; i < passes; i++) {
				if(BeginShaderPass(i)) {		
					
					DrawLineList(2 * (GRID_RESOULTION + 1));
					//for(UINT k = 0; k < 2 * 2 * (GRID_RESOULTION + 1); k+=2) {

					//	
					//	//CD3D9::g_pd3dDevice->DrawPrimitive(D3DPT_LINELIST,k, 1);
					//}
					EndShaderPass();
				}
			}
			EndShader();
		}
	}
}

void Render::ShowGrid(const bool visible) {

	try
	{
		if(visible && !Render::g_pVertexBuffer_Grid) { 
		
			struct tGridVertex {
				D3DXVECTOR3 position;
				D3DXVECTOR3 normal;
				D3DXVECTOR4 color;
			} g_GridVertices [GRIDVERTEX_COUNT];
				
			if(!Render::g_pGridShader) {
				if(!Render::Singleton()->GetShader("grid_shader.fx", (VERTEX_POSITION | VERTEX_NORMAL | VERTEX_COLOR), 
					CREATE_CHANNEL(CHANNEL_DECAL, 0, DIMENSIONS_2D), &Render::g_pGridShader))
					throw tException("Failed to create grid shader");
			}

			const uint sizeInBytes = GRIDVERTEX_COUNT * GetVertexFormatSize(Render::g_pGridShader->VertexFormat);
			if(!CreateVertexBuffer(sizeInBytes, false, &Render::g_pVertexBuffer_Grid))
				throw tException("Failed to allocate grid data");
	
			#define HALF_GRID_RESOULTION (float(GRID_RESOULTION) / 2.0f)
			for(UINT i = 0; i < (GRID_RESOULTION + 1); i++) {

				float x = float(i) - HALF_GRID_RESOULTION;
				g_GridVertices[i * 2 + 0].position = D3DXVECTOR3(x, 0.0f, HALF_GRID_RESOULTION);
				g_GridVertices[i * 2 + 0].normal = D3DXVECTOR3(0,1,0);
				g_GridVertices[i * 2 + 0].color = D3DXVECTOR4(69.0f/255.0f,69.0f/255.0f,67.0f/255.0f,255.0f/255.0f);
				g_GridVertices[i * 2 + 1].position = D3DXVECTOR3(x, 0.0f, - HALF_GRID_RESOULTION);
				g_GridVertices[i * 2 + 1].normal = D3DXVECTOR3(0,1,0);
				g_GridVertices[i * 2 + 1].color = D3DXVECTOR4(69.0f/255.0f,69.0f/255.0f,67.0f/255.0f,255.0f/255.0f);
			}

			for(UINT i = 0; i < (GRID_RESOULTION + 1); i++) {

				float z = float(i) - HALF_GRID_RESOULTION;
				g_GridVertices[(i + GRID_RESOULTION + 1) * 2 + 0].position = D3DXVECTOR3(HALF_GRID_RESOULTION, 0.0f, z);
				g_GridVertices[(i + GRID_RESOULTION + 1) * 2 + 0].normal = D3DXVECTOR3(0,1,0);
				g_GridVertices[(i + GRID_RESOULTION + 1) * 2 + 0].color = D3DXVECTOR4(68.0f/255.0f,69.0f/255.0f,67.0f/255.0f,255.0f/255.0f);
				g_GridVertices[(i + GRID_RESOULTION + 1) * 2 + 1].position = D3DXVECTOR3(-HALF_GRID_RESOULTION, 0.0f, z);
				g_GridVertices[(i + GRID_RESOULTION + 1) * 2 + 1].normal = D3DXVECTOR3(0,1,0);
				g_GridVertices[(i + GRID_RESOULTION + 1) * 2 + 1].color = D3DXVECTOR4(68.0f/255.0f,69.0f/255.0f,67.0f/255.0f,255.0f/255.0f);
			}

			if(!FillVertexBuffer(Render::g_pVertexBuffer_Grid, sizeInBytes, &g_GridVertices[0]))
				throw tException("Failed to fill grid data");


		}
		else if(!visible && Render::g_pVertexBuffer_Grid) {

			if(!ReleaseResource(&Render::g_pVertexBuffer_Grid))
				throw tException("Failed to release grid data");
		}
		else
			throw tException("Unknown state");
		
		return;
	}
	catch(tException exception) {

		if(Render::g_pVertexBuffer_Grid) {
			ReleaseResource(&Render::g_pVertexBuffer_Grid);
		}
	}
}

bool Render::ShadersEqual(Render::tShader* pA, Render::tShader* pB) {

	if(pA == NULL ^ pB == NULL )
		return false;

	if(!pA)
		return false;

	if(pA->Attributes != pB->Attributes)
		return false;

	if(pA->VertexFormat != pB->VertexFormat)
		return false;

	if(pA->filename.compare(pB->filename) != 0)
		return false;

	return true;
}

bool Render::ShadersEqual(Render::tShader* pA, std::string filenameB, uint VertexFormatB, uint AttributesB) {

	if(!pA)
		return false;

	if(pA->Attributes != AttributesB)
		return false;

	if(pA->VertexFormat != VertexFormatB)
		return false;

	if(pA->filename.compare(filenameB) != 0)
		return false;

	return true;
}

bool Render::GetDefaultShader(const uint VertexFormat, Render::tShader** pOut) {

	if(!pOut)
		return false;

	if(!Render::g_pDefaultShader) {
		if(!Render::Singleton()->GetShader("default_shader.fx", VertexFormat, CREATE_CHANNEL(CHANNEL_DECAL, 0, DIMENSIONS_2D), &Render::g_pDefaultShader)) {
			
			*pOut = NULL;
			return false;
		}
	}

	if(Render::g_pDefaultShader->VertexFormat == VertexFormat) {

		Render::Singleton()->UpdateShaderRefCount(Render::g_pDefaultShader, 1);
		*pOut = Render::g_pDefaultShader;
		return true;
	}
	
	if(!Render::Singleton()->GetShader(Render::g_pDefaultShader->filename, VertexFormat,Render::g_pDefaultShader->Attributes, pOut)){

		*pOut = NULL;
		return false;
	}

	return true;
}

bool Render::GetPrecached(const std::string& filename, const uint VertexFormat, const uint featureSet, Render::tShader** pOut) {

	if(!pOut) 
		return false;
	
	FOREACH(Render::tShader*, i, Render::g_Shaders) {

		Render::tShader *pElement = (*i);
		if(pElement) {
			if(	pElement->filename.compare(filename) == 0 &&
				pElement->VertexFormat == VertexFormat && 
				pElement->Attributes == featureSet) {

				Render::Singleton()->UpdateShaderRefCount(pElement, 1);
				*pOut = pElement;
				return true;
			}
		}
	}

	return false;
}

Render* Render::Singleton(Render::DeviceVersion version) {

	//TODO: if changing version reload resources?

	Render::g_lastVersion = version;

	switch(version) {

#ifdef BUILD_D3D9
		case Render::D3D_9: {
			static CD3D9* g_pD3D9 = NULL;
			if(g_pD3D9 == NULL)
				g_pD3D9 = new CD3D9();
			return g_pD3D9;
								   }
#endif
#ifdef BUILD_D3D10
		case Render::D3D_10: {
			static CD3D10* g_pD3D10 = NULL;
			if(g_pD3D10 == NULL)
				g_pD3D10 = new CD3D10();
			return g_pD3D10;
									}
#endif
	}

	return NULL;
}

void Render::SetCameraProjection(float fovRadians, float aspectRatio, float nearPlane, float farPlane) {

	D3DXMatrixPerspectiveFovLH( &Render::g_Camera.m_Proj, min(max(0.01f, fovRadians), D3DX_PI * 2.0f), aspectRatio, min(nearPlane,farPlane), max(nearPlane,farPlane));
}

void Render::GetCameraViewProjection(D3DXMATRIX* pViewProjection) {

	if(pViewProjection)
		*pViewProjection = Render::g_Camera.m_View * Render::g_Camera.m_Proj;
}

void Render::SetCameraFrameNavigation(D3DXVECTOR3& velocity, float yaw, float pitch) {

	float pitchAngle = Render::g_Camera.m_Angles[1] + pitch;

	if(pitchAngle > (D3DX_PI / 2.0f))
		pitchAngle = (D3DX_PI / 2.0f) - 0.001f;
	if(pitchAngle < -(D3DX_PI / 2.0f))
		pitchAngle = -(D3DX_PI / 2.0f) + 0.001f;


	Render::g_Camera.m_Angles[0] += yaw;
	Render::g_Camera.m_Angles[1] = pitchAngle;

	D3DXMATRIX yawMatrix;
	D3DXMatrixRotationY(&yawMatrix, Render::g_Camera.m_Angles[0]);

	D3DXVECTOR4 tmpRight;
	D3DXVECTOR4 tmpForward;
	D3DXVec3Transform(&tmpRight, &D3DXVECTOR3(1.0f,0.0f,0.0f), &yawMatrix);
	D3DXVec3Transform(&tmpForward, &D3DXVECTOR3(0.0f,0.0f,1.0f), &yawMatrix);

	D3DXMATRIX yawPitchMatrix;
	D3DXMatrixRotationAxis(&yawPitchMatrix, &D3DXVECTOR3((float*)&tmpRight), Render::g_Camera.m_Angles[1]);
	D3DXVec3Transform(&tmpForward, &D3DXVECTOR3((float*)&tmpForward), &yawPitchMatrix);

	Render::g_Camera.m_Right = D3DXVECTOR3((float*)&tmpRight);
	Render::g_Camera.m_Forward = D3DXVECTOR3((float*)&tmpForward);

	Render::g_Camera.m_Position += (Render::g_Camera.m_Right * -velocity.x);
	Render::g_Camera.m_Position += (Render::g_Camera.m_Forward * velocity.z);

	D3DXVECTOR3 at = Render::g_Camera.m_Position + Render::g_Camera.m_Forward;
	D3DXMatrixLookAtLH(&Render::g_Camera.m_View, &Render::g_Camera.m_Position,  &at, &D3DXVECTOR3(0,1,0));
}

void Render::SetCameraPositionDirection(D3DXVECTOR3& position, D3DXVECTOR3& direction) {

	D3DXVec3Normalize(&direction,&direction);

	Render::g_Camera.m_Angles[0] = atanf(direction.x / direction.z);
	Render::g_Camera.m_Angles[1] = atanf(direction.y / sqrtf(direction.x*direction.x + direction.z*direction.z));

	D3DXVECTOR3 tmpRight;
	D3DXVec3Cross(&tmpRight, &D3DXVECTOR3(0.0f,1.0f,0.0f), &direction);

	Render::g_Camera.m_Right = tmpRight;
	Render::g_Camera.m_Forward = direction;

	Render::g_Camera.m_Position = position;

	D3DXVECTOR3 at = Render::g_Camera.m_Position + Render::g_Camera.m_Forward;
	D3DXMatrixLookAtLH(&Render::g_Camera.m_View, &Render::g_Camera.m_Position,  &at, &D3DXVECTOR3(0,1,0));
}